import random
import time
import uuid
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit, join_room

app = Flask(__name__, static_folder='./static')
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

# 卡牌配置
ATOMS = ['H', 'C', 'O', 'N', 'F', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'K', 'Ca',
         'Mn', 'Fe', 'Cu', 'Zn', 'Li', 'Ag', 'I', 'Ba', 'Ar', 'He', 'Ne']
ACID_RADICALS = ['OH', 'SO4', 'HSO4', 'NO3', 'CO3', 'HCO3', 'NH4', 'PO4']
ACTION_CARDS = ['点燃', '加热', '催化剂', '通电']

# 所有可能的卡牌
ALL_CARDS = ATOMS + ACID_RADICALS + ACTION_CARDS

# 游戏房间管理
rooms = {}
players = {}


class Player:
    def __init__(self, sid, name, room_id, is_teacher=False):
        self.sid = sid
        self.name = name
        self.room_id = room_id
        self.is_teacher = is_teacher
        self.role = "化学老师" if is_teacher else "普通化学实验者"

        # 化学老师不参与游戏
        if not is_teacher:
            self.hand_cards = []
            self.reaction_area = []
            self.floor_area = []
            self.health = 8
            self.is_ready = False
            self.eliminated = False
        else:
            # 化学老师没有游戏属性
            self.hand_cards = None
            self.reaction_area = None
            self.floor_area = None
            self.health = None
            self.is_ready = True  # 化学老师始终准备就绪

        self.pending_approvals = []


class Room:
    def __init__(self, room_id, teacher_sid):
        self.room_id = room_id
        self.players = {}
        self.teacher_sid = teacher_sid  # 化学老师的SID
        self.game_state = "waiting"  # waiting, distributing, preparation, action, defense, settlement
        self.current_turn = None
        self.turn_order = []
        self.attack_targets = {}  # 当前回合的攻击目标 {攻击者SID: 目标SID}
        self.defense_actions = {}  # 防御操作 {防御者SID: 防御信息}
        self.turn_count = 0
        self.current_attacker = None  # 当前攻击者

    def add_player(self, player):
        self.players[player.sid] = player
        self.turn_order.append(player.sid)

    def start_game(self):
        if len([p for p in self.players.values() if not p.is_teacher]) < 2:
            return False

        self.game_state = "distributing"
        # 找到第一个普通玩家作为当前回合
        for sid in self.turn_order:
            if not self.players[sid].is_teacher:
                self.current_turn = sid
                break
        self.turn_count = 0

        # 分配初始手牌
        for sid, player in self.players.items():
            if not player.is_teacher:
                player.hand_cards = self.draw_cards(6)

        return True

    def draw_cards(self, count):
        """随机分配卡牌，原子和酸根概率更高"""
        cards = []
        weights = [2 if card in (ATOMS + ACID_RADICALS) else 1 for card in ALL_CARDS]

        cards = set()
        while len(cards) < count:
            card = random.choices(ALL_CARDS, weights=weights, k=1)[0]
            cards.add(card)

        return cards

    def get_player_under_attack(self, player_sid):
        """检查玩家是否被攻击"""
        return any(target == player_sid for target in self.attack_targets.values())

    def next_phase(self):
        """推进到下一阶段"""
        if self.game_state == "distributing":
            self.game_state = "preparation"
            # 重置准备状态
            for sid, player in self.players.items():
                if not player.is_teacher:
                    player.is_ready = False

        elif self.game_state == "preparation":
            # 检查所有玩家是否准备完毕
            all_ready = all(
                player.is_ready or player.eliminated or player.is_teacher
                for player in self.players.values()
            )

            if all_ready:
                self.game_state = "action"
                # 重置攻击目标
                self.attack_targets = {}
                self.current_attacker = None

        elif self.game_state == "action":
            # 检查是否有玩家发起了攻击
            if self.attack_targets:
                self.game_state = "defense"
            else:
                # 没有攻击则直接进入结算阶段
                self.game_state = "settlement"
                self.settle_game()
            # 重置防御操作
            self.defense_actions = {}

        elif self.game_state == "defense":
            # 检查所有被攻击玩家是否完成防御
            targets = set(self.attack_targets.values())
            if any(target not in self.defense_actions for target in targets):
                return
            self.game_state = "settlement"
            self.settle_game()

        elif self.game_state == "settlement":
            # 重置攻击和防御状态
            self.attack_targets = {}
            self.defense_actions = {}
            self.current_attacker = None
            # 清空所有玩家的地板区
            for player in self.players.values():
                if not player.is_teacher:
                    player.floor_area = []

            # 检查游戏是否结束
            if not self.check_game_over():
                self.turn_count += 1
                self.next_turn()
                self.game_state = "distributing"

                # 为所有玩家发新牌
                for sid, player in self.players.items():
                    if not player.eliminated and not player.is_teacher:
                        player.hand_cards = self.draw_cards(6)

        # 移除淘汰玩家的回合
        self.turn_order = [sid for sid in self.turn_order
                           if not self.players[sid].eliminated]
        return self.game_state

    def next_turn(self):
        """切换到下一个玩家回合，跳过化学老师"""
        if not self.turn_order:
            return

        current_index = self.turn_order.index(self.current_turn)
        next_index = (current_index + 1) % len(self.turn_order)

        # 查找下一个未淘汰的普通玩家
        for _ in range(len(self.turn_order)):
            next_player_sid = self.turn_order[next_index]
            player = self.players[next_player_sid]
            if not player.eliminated and not player.is_teacher:
                self.current_turn = next_player_sid
                return
            next_index = (next_index + 1) % len(self.turn_order)

    def settle_game(self):
        """结算伤害"""
        # 处理攻击结算
        for attacker_sid, target_sid in self.attack_targets.items():
            if target_sid is None:  # 跳过攻击
                continue

            attacker = self.players.get(attacker_sid)
            target = self.players.get(target_sid)

            if not attacker or not target or target.eliminated:
                continue

            # 获取防御动作
            defense_info = self.defense_actions.get(target.sid, {})

            # 计算伤害
            damage = self.calculate_damage(attacker.floor_area, defense_info)
            target.health = max(0, target.health - damage)

            # 检查是否淘汰
            if target.health <= 0:
                target.eliminated = True

    def calculate_damage(self, attack_materials, defense_info):
        """计算伤害值"""
        damage = 0
        defense_action = defense_info.get('action', '')
        defense_compound = defense_info.get('compound', {})
        defense_gas = defense_info.get('gas', '')

        # 攻击物质伤害
        for material in attack_materials:
            props = material.get('properties', {})

            # 浓腐蚀性物质
            if props.get('corrosive_concentrated'):
                if defense_action == 'dilute':
                    damage += 1  # 稀释后伤害
                else:
                    damage += 2

            # 稀腐蚀性物质
            elif props.get('corrosive_diluted'):
                damage += 1

            # 中毒伤害
            elif props.get('poison'):
                damage += 3

            # 气体伤害（除非使用稀有气体防御）
            elif props.get('gas') and defense_gas not in ['Ar', 'He', 'Ne']:
                damage += 2

        # 防御减免
        if defense_action == 'use_existing' and defense_compound:
            defense_props = defense_compound.get('properties', {})

            # 根据防御物质属性减少伤害
            if defense_props.get('corrosive_concentrated'):
                damage = max(0, damage - 2)
            elif defense_props.get('corrosive_diluted'):
                damage = max(0, damage - 1)
            elif defense_props.get('poison'):
                damage = max(0, damage - 3)

        return damage

    def broadcast_state(self):
        state = {
            'game_state': self.game_state,
            'current_turn': self.current_turn,
            'players': {},
            'attack_targets': self.attack_targets
        }

        for sid, player in self.players.items():
            # 化学老师也需要接收完整游戏状态
            player_data = {
                'name': player.name,
                'role': player.role,
                'is_teacher': player.is_teacher,
                'is_ready': player.is_ready,
            }

            # 普通玩家添加游戏数据
            if not player.is_teacher:
                player_data.update({
                    'eliminated': player.eliminated,
                    'health': player.health,
                    'hand_cards_count': len(player.hand_cards),
                    'reaction_area': player.reaction_area,
                    'floor_area': player.floor_area,
                    'is_under_attack': self.get_player_under_attack(sid)
                })
            else:  # 化学老师也需要知道淘汰状态
                player_data['eliminated'] = False

            state['players'][sid] = player_data

        emit('game_state', state, room=self.room_id)

    def check_game_over(self):
        """检查游戏是否结束"""
        active_players = [p for p in self.players.values() if not p.eliminated and not p.is_teacher]

        if len(active_players) == 1:
            winner = active_players[0]
            emit('game_over', {'winner': winner.name}, room=self.room_id)
            return True
        elif len(active_players) == 0:
            emit('game_draw', {}, room=self.room_id)
            return True

        return False


# 路由和SocketIO事件处理
@app.route('/')
def index():
    return render_template('index.html')


@app.route('/game/<room_id>')
def game_page(room_id):
    return render_template('game.html', room_id=room_id)


@app.route('/teacher/<room_id>')
def teacher_page(room_id):
    return render_template('teacher.html', room_id=room_id)


@socketio.on('create_room')
def handle_create_room(data):
    sid = request.sid
    name = data.get('name')
    # 使用UUID代替时间戳生成房间ID
    room_id = str(uuid.uuid4())

    # 创建房间和玩家（房主是化学老师）
    teacher = Player(sid, name, room_id, is_teacher=True)
    room = Room(room_id, sid)
    room.add_player(teacher)

    rooms[room_id] = room
    players[sid] = teacher

    join_room(room_id)
    emit('room_created', {'room_id': room_id, 'teacher': True})
    # 只返回重定向到老师页面
    emit('redirect', {'url': f'/teacher/{room_id}'})


@socketio.on('join_room')
def handle_join_room(data):
    sid = request.sid
    name = data.get('name')
    room_id = data.get('room_id')

    if room_id not in rooms:
        emit('error', {'message': '房间不存在'})
        return

    room = rooms[room_id]

    if len(room.players) >= 6:
        emit('error', {'message': '房间已满'})
        return

    player = Player(sid, name, room_id)
    room.add_player(player)
    players[sid] = player

    join_room(room_id)
    room.broadcast_state()

    # 明确返回加入房间信息
    emit('joined_room', {
        'room_id': room_id,
        'teacher': False,
        'player_count': len(room.players)
    }, room=sid)

    # 保留原有重定向机制
    emit('redirect', {'url': f'/game/{room_id}'}, room=sid)


@socketio.on('connect_to_room')
def handle_connect_to_room(data):
    sid = request.sid
    room_id = data.get('room_id')
    is_teacher = data.get('is_teacher', False)

    if room_id in rooms:
        room = rooms[room_id]
        # 如果玩家已存在，更新其SID
        if sid in room.players:
            player = room.players[sid]
        else:
            # 如果是老师，则使用房间中已有的老师对象
            if is_teacher and room.teacher_sid:
                player = room.players.get(room.teacher_sid)
                if player:
                    player.sid = sid  # 更新SID（老师重新连接）
                else:
                    # 如果找不到老师，创建一个
                    player = Player(sid, "化学老师", room_id, is_teacher=True)
                    room.add_player(player)
            else:
                # 新玩家
                name = f"玩家{len(room.players)}"
                player = Player(sid, name, room_id)
                room.add_player(player)

            players[sid] = player

        # 发送当前游戏状态
        room.broadcast_state()
        # 告知客户端自己的SID
        emit('my_sid', sid, room=sid)
    else:
        emit('error', {'message': '房间不存在'})


@socketio.on('start_game')
def handle_start_game():
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只有化学老师可以开始游戏
    if sid != room.teacher_sid:
        emit('error', {'message': '只有化学老师可以开始游戏'})
        return

    if room.start_game():
        room.broadcast_state()
    else:
        emit('error', {'message': '需要至少2名普通玩家才能开始游戏'})


@socketio.on('prepare_compound')
def handle_prepare_compound(data):
    sid = request.sid
    player = players.get(sid)
    is_for_defense = data.get('is_for_defense', False)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    compound = data.get('compound')
    cards_used = data.get('cards_used', [])

    # 检查使用的卡牌是否在玩家手牌中
    if not all(card in player.hand_cards for card in cards_used):
        emit('error', {'message': '使用了不存在的手牌'})
        return

    # 提交化学老师审核
    approval_request = {
        'type': 'defense_compound' if is_for_defense else 'compound',
        'player_sid': sid,
        'compound': compound,
        'cards_used': cards_used,
        'timestamp': time.time(),
        'is_for_defense': is_for_defense
    }

    # 添加到玩家待审核列表
    player.pending_approvals.append(approval_request)

    # 通知化学老师
    emit('approval_request', approval_request, room=room.teacher_sid)


@socketio.on('prepare_reaction')
def handle_prepare_reaction(data):
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只能在准备阶段操作
    if room.game_state != 'preparation':
        emit('error', {'message': '不在准备阶段'})
        return

    equation = data.get('equation')
    reactants = data.get('reactants', [])
    products = data.get('products', [])
    action_cards = data.get('action_cards', [])

    # 检查反应物是否在玩家反应区
    if not all(any(r['id'] == reactant_id for r in player.reaction_area) for reactant_id in reactants):
        emit('error', {'message': '使用了不存在的反应物'})
        return

    # 提交化学老师审核
    approval_request = {
        'type': 'reaction',
        'player_sid': sid,
        'equation': equation,
        'reactants': reactants,
        'products': products,
        'action_cards': action_cards,
        'timestamp': time.time()
    }

    # 添加到玩家待审核列表
    player.pending_approvals.append(approval_request)

    # 通知化学老师
    emit('approval_request', approval_request, room=room.teacher_sid)


@socketio.on('approve_request')
def handle_approve_request(data):
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms or player.role != "化学老师":
        emit('error', {'message': '无操作权限'})
        return

    room = rooms[player.room_id]
    approval_id = data.get('id')
    approved = data.get('approved')
    properties = data.get('properties', {})

    # 查找待处理的审核请求
    target_player = None
    request_data = None
    for p in room.players.values():
        for req in p.pending_approvals:
            if req['timestamp'] == approval_id:
                target_player = p
                request_data = req
                break
        if target_player:
            break
    if request_data['phase'] != room.game_state:
        emit('error', {'message': '审核请求已过期'})
        return
    if not target_player or not request_data:
        emit('error', {'message': '审核请求不存在'})
        return

    # 从待审核列表移除
    target_player.pending_approvals = [req for req in target_player.pending_approvals
                                       if req['timestamp'] != approval_id]

    if approved:
        # 处理不同类型审核
        if request_data['type'] == 'compound':
            # 添加化合物到反应区
            compound = {
                'id': time.time(),
                'formula': request_data['compound'],
                'properties': properties
            }
            target_player.reaction_area.append(compound)

            # 移除使用的手牌
            for card in request_data['cards_used']:
                if card in target_player.hand_cards:
                    target_player.hand_cards.remove(card)

        elif request_data['type'] == 'reaction':
            # 移除反应物
            for reactant_id in request_data['reactants']:
                target_player.reaction_area = [
                    c for c in target_player.reaction_area
                    if c['id'] != reactant_id
                ]

            # 添加生成物
            for product in request_data['products']:
                product['id'] = time.time()
                product['properties'] = properties.get(product['formula'], {})
                target_player.reaction_area.append(product)

            # 移除使用的操作牌
            for action_card in request_data['action_cards']:
                if action_card in target_player.hand_cards:
                    target_player.hand_cards.remove(action_card)

        # 处理防御化合物
        if request_data.get('is_for_defense'):
            # 如果是防御化合物，添加到地板区
            compound = {
                'id': time.time(),
                'formula': request_data['compound'],
                'properties': properties
            }
            target_player.floor_area.append(compound)

            # 更新防御动作
            room.defense_actions[target_player.sid] = {
                'action': 'use_existing',
                'compound': compound
            }

    # 通知申请玩家结果
    emit('approval_result', {
        'approved': approved,
        'type': request_data['type'],
        'id': approval_id
    }, room=target_player.sid)

    room.broadcast_state()


@socketio.on('end_preparation')
def handle_end_preparation():
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只能在准备阶段操作
    if room.game_state != 'preparation':
        emit('error', {'message': '不在准备阶段'})
        return

    player.is_ready = True
    room.broadcast_state()

    # 检查是否所有玩家准备完毕
    if all(p.is_ready or p.eliminated or p.is_teacher for p in room.players.values()):
        room.next_phase()
        room.broadcast_state()


@socketio.on('attack')
def handle_attack(data):
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只能在行动阶段操作
    if room.game_state != 'action':
        emit('error', {'message': '不在行动阶段'})
        return

    # 检查是否是当前回合玩家
    if sid != room.current_turn:
        emit('error', {'message': '不是你的回合'})
        return

    target_sid = data.get('target_sid')
    compound_id = data.get('compound_id')

    # 检查目标玩家是否存在且未淘汰
    target = room.players.get(target_sid)
    if not target or target.eliminated or target.is_teacher:
        emit('error', {'message': '目标玩家无效'})
        return

    # 检查化合物是否在玩家反应区
    compound = next((c for c in player.reaction_area if c['id'] == compound_id), None)
    if not compound:
        emit('error', {'message': '化合物不存在'})
        return

    # 添加到攻击目标
    room.attack_targets[sid] = target_sid
    room.current_attacker = sid

    # 将化合物移动到地板区
    player.floor_area.append(compound)
    player.reaction_area = [c for c in player.reaction_area if c['id'] != compound_id]

    room.broadcast_state()
    emit('attack_made', {
        'attacker': player.name,
        'target': target.name,
        'compound': compound
    }, room=room.room_id)


@socketio.on('defend')
def handle_defend(data):
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只能在防御阶段操作
    if room.game_state != 'defense':
        emit('error', {'message': '不在防御阶段'})
        return

    # 检查是否被攻击
    if sid not in room.attack_targets.values() or room.attack_targets.get(room.current_attacker) != sid:
        emit('error', {'message': '你未被攻击'})
        return

    action = data.get('action')
    defense_data = {'action': action}

    # 根据防御类型处理
    if action == 'use_existing':
        compound_id = data.get('compound_id')
        compound = next((c for c in player.reaction_area if c['id'] == compound_id), None)
        if compound:
            # 使用现有化合物防御
            defense_data['compound'] = compound
            player.floor_area.append(compound)
            player.reaction_area = [c for c in player.reaction_area if c['id'] != compound_id]
        else:
            emit('error', {'message': '化合物不存在'})
            return

    elif action == 'use_gas':
        # 使用稀有气体防御
        gas = data.get('gas')
        if gas not in player.hand_cards or gas not in ['Ar', 'He', 'Ne']:
            emit('error', {'message': '无效的稀有气体'})
            return

        defense_data['gas'] = gas
        player.hand_cards.remove(gas)

    elif action == 'dilute':
        # 稀释腐蚀性物质
        water_id = data.get('water_id')
        water = next((c for c in player.reaction_area if c['id'] == water_id and c['formula'] == 'H2O'), None)
        if not water:
            emit('error', {'message': '没有可用水'})
            return

        defense_data['water'] = water
        player.reaction_area = [c for c in player.reaction_area if c['id'] != water_id]

    elif action == 'new_compound':
        # 使用新化合物防御（需要审核）
        compound = data.get('compound')
        cards_used = data.get('cards_used', [])

        # 检查使用的卡牌是否在玩家手牌中
        if not all(card in player.hand_cards for card in cards_used):
            emit('error', {'message': '使用了不存在的手牌'})
            return

        # 提交化学老师审核
        approval_request = {
            'type': 'defense_compound',
            'player_sid': sid,
            'compound': compound,
            'cards_used': cards_used,
            'timestamp': time.time(),
            'is_for_defense': True,
            'phase' : room.game_state
        }

        player.pending_approvals.append(approval_request)
        defense_data['pending_approval'] = True

        # 通知化学老师
        emit('approval_request', approval_request, room=room.teacher_sid)

    # 保存防御操作
    room.defense_actions[sid] = defense_data
    room.broadcast_state()


@socketio.on('self_rescue')
def handle_self_rescue(data):
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 只能在准备阶段操作
    if room.game_state != 'preparation':
        emit('error', {'message': '不在准备阶段'})
        return

    if player.health > 7:  # 是否需要检查
        emit('error', {'message': '未处于病危状态'})
        return

    resource = data.get('resource')  # 'oxygen' 或 'water'

    if resource == 'oxygen':
        # 检查是否有氧气
        oxygen = next((c for c in player.reaction_area if c['formula'] == 'O2'), None)
        if oxygen:
            player.reaction_area.remove(oxygen)
            player.health = min(8, player.health + 3)
            emit('self_rescue_success', {'resource': 'oxygen', 'health': player.health}, room=sid)
        if not oxygen:
            emit('error', {'message': '没有可用氧气'})
            return

    elif resource == 'water':
        # 检查是否有水
        water = next((c for c in player.reaction_area if c['formula'] == 'H2O'), None)
        if water:
            player.reaction_area.remove(water)
            player.health = min(8, player.health + 3)
            emit('self_rescue_success', {'resource': 'water', 'health': player.health}, room=sid)

    room.broadcast_state()


@socketio.on('skip')
def handle_skip():
    sid = request.sid
    player = players.get(sid)

    if not player or player.room_id not in rooms:
        return

    room = rooms[player.room_id]

    # 在行动阶段跳过
    if room.game_state == 'action' and sid == room.current_turn:
        room.attack_targets[sid] = None  # 标记为跳过攻击
        room.next_phase()
        room.broadcast_state()

    # 在防御阶段跳过
    elif room.game_state == 'defense' and sid in room.attack_targets.values():
        room.defense_actions[sid] = {'action': 'skip'}
        room.broadcast_state()

        # 推进到下一个阶段
        room.next_phase()
        room.broadcast_state()


@socketio.on('disconnect')
def handle_disconnect():
    sid = request.sid
    player = players.get(sid)

    if player and player.room_id in rooms:
        room = rooms[player.room_id]

        # 从房间移除玩家
        if sid in room.players:
            del room.players[sid]

        # 如果房间空了，移除房间
        if not room.players:
            if room.game_state != 'waiting':
                del rooms[player.room_id]
        else:
            # 如果化学老师离开，指定新化学老师
            if sid == room.teacher_sid and room.players:
                new_teacher_sid = next(iter(room.players.keys()))
                room.teacher_sid = new_teacher_sid
                room.players[new_teacher_sid].role = "化学老师"
                room.players[new_teacher_sid].is_teacher = True

            room.broadcast_state()

        del players[sid]

    if room.teacher_sid:
        for player in room.players.values():
            for req in player.pending_approvals:
                emit('approval_request', req, room=room.teacher_sid)


if __name__ == '__main__':
    socketio.run(app, debug=True, allow_unsafe_werkzeug=True, host='0.0.0.0', port=5200)
